package com.guomei.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;

/**
 * 微信公众号消息加解密工具类
 * 参考文档：https://developers.weixin.qq.com/doc/service/guide/dev/push/encryption.html
 * 
 * @author guomei
 */
@Slf4j
public class WechatCryptoUtil {
    
    private static final String ALGORITHM = "AES";
    private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
    private static final String CHARSET = "UTF-8";
    
    /**
     * 验证签名
     * @param token 微信公众号配置的token
     * @param timestamp 时间戳
     * @param nonce 随机数
     * @param signature 微信传过来的签名
     * @return 验证结果
     */
    public static boolean verifySignature(String token, String timestamp, String nonce, String signature) {
        try {
            String[] strs = {token, timestamp, nonce};
            Arrays.sort(strs);
            
            String combined = String.join("", strs);
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            byte[] hash = digest.digest(combined.getBytes(StandardCharsets.UTF_8));
            
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            
            boolean result = hexString.toString().equals(signature);
            log.info("签名验证结果: {}, 计算签名: {}, 接收签名: {}", result, hexString.toString(), signature);
            return result;
        } catch (NoSuchAlgorithmException e) {
            log.error("签名验证失败", e);
            return false;
        }
    }
    
    /**
     * 验证消息体签名
     * @param token 微信公众号配置的token  
     * @param timestamp 时间戳
     * @param nonce 随机数
     * @param encrypt 加密的消息体
     * @param msgSignature 消息体签名
     * @return 验证结果
     */
    public static boolean verifyMsgSignature(String token, String timestamp, String nonce, String encrypt, String msgSignature) {
        try {
            String[] strs = {token, timestamp, nonce, encrypt};
            Arrays.sort(strs);
            
            String combined = String.join("", strs);
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            byte[] hash = digest.digest(combined.getBytes(StandardCharsets.UTF_8));
            
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            
            boolean result = hexString.toString().equals(msgSignature);
            log.info("消息体签名验证结果: {}", result);
            return result;
        } catch (NoSuchAlgorithmException e) {
            log.error("消息体签名验证失败", e);
            return false;
        }
    }
    
    /**
     * 解密微信消息
     * @param encodingAESKey EncodingAESKey
     * @param encrypt 加密的消息体
     * @param appId 微信公众号AppId
     * @return 解密后的消息内容
     */
    public static String decrypt(String encodingAESKey, String encrypt, String appId) {
        try {
            // 1. 解码Base64
            byte[] aesKey = Base64.decodeBase64(encodingAESKey + "=");
            byte[] encryptData = Base64.decodeBase64(encrypt);
            
            // 2. AES解密
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, ALGORITHM);
            IvParameterSpec ivSpec = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
            
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            byte[] decrypted = cipher.doFinal(encryptData);
            
            // 3. 去除随机字符串和长度
            // 格式：random(16B) + msg_len(4B) + msg + appid
            byte[] random = Arrays.copyOfRange(decrypted, 0, 16);
            byte[] msgLenBytes = Arrays.copyOfRange(decrypted, 16, 20);
            
            // 网络字节序转换
            int msgLen = ByteBuffer.wrap(msgLenBytes).getInt();
            
            byte[] msgBytes = Arrays.copyOfRange(decrypted, 20, 20 + msgLen);
            byte[] appIdBytes = Arrays.copyOfRange(decrypted, 20 + msgLen, decrypted.length);
            
            String msg = new String(msgBytes, StandardCharsets.UTF_8);
            String receivedAppId = new String(appIdBytes, StandardCharsets.UTF_8);
            
            // 验证AppId
            if (!appId.equals(receivedAppId)) {
                log.error("AppId验证失败，期望: {}, 实际: {}", appId, receivedAppId);
                return null;
            }
            
            log.info("消息解密成功，内容长度: {}", msg.length());
            return msg;
            
        } catch (Exception e) {
            log.error("消息解密失败", e);
            return null;
        }
    }
    
    /**
     * 加密回复消息
     * @param encodingAESKey EncodingAESKey
     * @param replyMsg 回复消息内容
     * @param appId 微信公众号AppId
     * @return 加密后的消息体
     */
    public static String encrypt(String encodingAESKey, String replyMsg, String appId) {
        try {
            // 1. 生成随机字符串
            byte[] random = generateRandom(16);
            
            // 2. 构造消息体
            byte[] msgBytes = replyMsg.getBytes(StandardCharsets.UTF_8);
            byte[] appIdBytes = appId.getBytes(StandardCharsets.UTF_8);
            
            // 消息长度（网络字节序）
            ByteBuffer msgLenBuffer = ByteBuffer.allocate(4);
            msgLenBuffer.putInt(msgBytes.length);
            byte[] msgLenBytes = msgLenBuffer.array();
            
            // 拼接：random(16B) + msg_len(4B) + msg + appid
            ByteBuffer fullMsg = ByteBuffer.allocate(random.length + msgLenBytes.length + msgBytes.length + appIdBytes.length);
            fullMsg.put(random);
            fullMsg.put(msgLenBytes);
            fullMsg.put(msgBytes);
            fullMsg.put(appIdBytes);
            
            // 3. AES加密
            byte[] aesKey = Base64.decodeBase64(encodingAESKey + "=");
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, ALGORITHM);
            IvParameterSpec ivSpec = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
            
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
            byte[] encrypted = cipher.doFinal(fullMsg.array());
            
            // 4. Base64编码
            String result = Base64.encodeBase64String(encrypted);
            log.info("消息加密成功，原文长度: {}, 密文长度: {}", replyMsg.length(), result.length());
            return result;
            
        } catch (Exception e) {
            log.error("消息加密失败", e);
            return null;
        }
    }
    
    /**
     * 生成消息体签名
     * @param token 微信公众号配置的token
     * @param timestamp 时间戳  
     * @param nonce 随机数
     * @param encrypt 加密的消息体
     * @return 签名
     */
    public static String generateMsgSignature(String token, String timestamp, String nonce, String encrypt) {
        try {
            String[] strs = {token, timestamp, nonce, encrypt};
            Arrays.sort(strs);
            
            String combined = String.join("", strs);
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            byte[] hash = digest.digest(combined.getBytes(StandardCharsets.UTF_8));
            
            StringBuilder hexString = new StringBuilder();
            for (byte b : hash) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0');
                }
                hexString.append(hex);
            }
            
            return hexString.toString();
        } catch (NoSuchAlgorithmException e) {
            log.error("生成消息体签名失败", e);
            return null;
        }
    }
    
    /**
     * 生成随机字节数组
     * @param length 长度
     * @return 随机字节数组
     */
    private static byte[] generateRandom(int length) {
        byte[] random = new byte[length];
        new SecureRandom().nextBytes(random);
        return random;
    }
}
